package com.welson.db.cryptor.util;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.welson.db.cryptor.config.DbCryptorProperties;
import com.welson.db.cryptor.strategy.CryptStrategy;
import com.welson.db.cryptor.strategy.CryptStrategyFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.concurrent.TimeUnit;

/**
 * 加解密工具类
 * @author welson
 */
@Slf4j
@Component
public class Cryptor {
    private static DbCryptorProperties properties;
    private static CryptStrategyFactory strategyFactory;
    private static CryptStrategy strategy;
    private static Cache<String, String> encryptedValueCache;
    private static Cache<String, String> decryptedValueCache;

    public Cryptor(DbCryptorProperties properties, CryptStrategyFactory strategyFactory) {
        Cryptor.properties = properties;
        Cryptor.strategyFactory = strategyFactory;
    }

    @PostConstruct
    public void init() {
        Cryptor.strategy = strategyFactory.getStrategy(properties.getAlgorithm());
        initCaches();
    }

    private void initCaches() {
        // 初始化加密缓存
        CacheBuilder<Object, Object> encryptCacheBuilder = CacheBuilder.newBuilder()
                .maximumSize(properties.getEncryptCache().getSize());
        if (properties.getEncryptCache().getExpireMinutes() > 0) {
            encryptCacheBuilder.expireAfterWrite(properties.getEncryptCache().getExpireMinutes(), TimeUnit.MINUTES);
        }
        encryptedValueCache = encryptCacheBuilder.build();

        // 初始化解密缓存
        CacheBuilder<Object, Object> decryptCacheBuilder = CacheBuilder.newBuilder()
                .maximumSize(properties.getDecryptCache().getSize());
        if (properties.getDecryptCache().getExpireMinutes() > 0) {
            decryptCacheBuilder.expireAfterWrite(properties.getDecryptCache().getExpireMinutes(), TimeUnit.MINUTES);
        }
        decryptedValueCache = decryptCacheBuilder.build();
    }

    /**
     * 获取IV长度
     * @return IV长度（字节）
     */
    private static int getIvLength() {
        String mode = properties.getMode().toUpperCase();
        
        // AES和SM4的块大小都是16字节（128位）
        int blockSize = 16;
        
        // 对于GCM模式，IV长度通常为12字节，但允许任意长度（小于等于2^64-1且为8的倍数）
        if ("GCM".equals(mode)) {
            // 如果配置了IV，使用配置的长度
            if (properties.getIv() != null) {
                return properties.getIv().getBytes().length;
            }
            // 默认使用12字节（96位），这是GCM模式推荐的长度
            return 12;
        }
        
        // 对于CFB模式，IV长度可以小于块大小
        if ("CFB".equals(mode)) {
            // 默认使用块大小，但可以根据需要调整
            return blockSize;
        }
        
        // 对于其他需要IV的模式，IV长度必须等于块大小
        return blockSize;
    }

    /**
     * 判断当前模式是否需要IV
     * @return 是否需要IV
     */
    private static boolean isModeRequireIv() {
        String algorithm = properties.getAlgorithm().toUpperCase();
        String mode = properties.getMode().toUpperCase();
        
        // GCM模式仅AES支持
        if ("GCM".equals(mode)) {
            if ("SM4".equals(algorithm)) {
                throw new IllegalArgumentException("SM4算法不支持GCM模式");
            }
            return true;
        }
        
        // 需要IV的模式列表
        return Arrays.asList("CBC", "CFB", "OFB", "CTR").contains(mode);
    }

    /**
     * 加密
     *
     * @param data 待加密数据
     * @return 加密后的数据
     */
    public static String encrypt(String data) {
        if (StringUtils.isBlank(data)) {
            return data;
        }
        try {
            // 检查缓存
            String cachedValue = encryptedValueCache.getIfPresent(data);
            if (cachedValue != null) {
                return cachedValue;
            }

            // 执行加密
            String result = doEncrypt(data);
            
            // 更新缓存
            encryptedValueCache.put(data, result);
            return result;
        } catch (Exception e) {
            log.error("加密失败", e);
            return data;
        }
    }

    /**
     * 解密
     *
     * @param data 待解密数据
     * @return 解密后的数据
     */
    public static String decrypt(String data) {
        if (StringUtils.isBlank(data)) {
            return data;
        }
        try {
            // 检查缓存
            String cachedValue = decryptedValueCache.getIfPresent(data);
            if (cachedValue != null) {
                return cachedValue;
            }

            // 执行解密
            String result = doDecrypt(data);
            
            // 更新缓存
            decryptedValueCache.put(data, result);
            return result;
        } catch (Exception e) {
            log.error("解密失败", e);
            return data;
        }
    }

    /**
     * 执行加密操作
     */
    private static String doEncrypt(String data) throws Exception {
        byte[] keyBytes = properties.getKey().getBytes(StandardCharsets.UTF_8);
        byte[] ivBytes = null;

        if (isModeRequireIv()) {
            if (StringUtils.isBlank(properties.getIv())) {
                throw new IllegalArgumentException("IV不能为空");
            }
            ivBytes = properties.getIv().getBytes(StandardCharsets.UTF_8);
            validateIvLength(ivBytes);
        }

        byte[] encrypted = strategy.encrypt(data.getBytes(StandardCharsets.UTF_8), keyBytes, ivBytes);
        return Base64.getEncoder().encodeToString(encrypted);
    }

    /**
     * 执行解密操作
     */
    private static String doDecrypt(String data) throws Exception {
        byte[] keyBytes = properties.getKey().getBytes(StandardCharsets.UTF_8);
        byte[] encryptedData = Base64.getDecoder().decode(data);
        byte[] ivBytes = null;

        if (isModeRequireIv()) {
            if (StringUtils.isBlank(properties.getIv())) {
                throw new IllegalArgumentException("IV不能为空");
            }
            ivBytes = properties.getIv().getBytes(StandardCharsets.UTF_8);
        }

        byte[] decrypted = strategy.decrypt(encryptedData, keyBytes, ivBytes);
        return new String(decrypted, StandardCharsets.UTF_8);
    }

    /**
     * 验证IV长度
     */
    private static void validateIvLength(byte[] ivBytes) {
        String mode = properties.getMode().toUpperCase();
        int requiredLength = getIvLength();
        
        if ("GCM".equals(mode)) {
            // GCM模式：IV长度必须小于等于2^64-1且为8的倍数
            if (ivBytes.length % 8 != 0) {
                throw new IllegalArgumentException(
                    String.format("GCM模式下IV长度必须为8的倍数且小于等于%d字节，当前长度为%d字节", 
                        Long.MAX_VALUE, ivBytes.length));
            }
        } else {
            // 其他模式：IV长度必须等于要求长度
            if (ivBytes.length != requiredLength) {
                throw new IllegalArgumentException(
                    String.format("IV长度必须为%d字节，当前长度为%d字节", requiredLength, ivBytes.length));
            }
        }
    }
}